#include "TextSpeak.h"

#include "SoundTouch.h"

TextSpeak::TextSpeak() : speedIncrease_(1.0f), volumnIncrease_(1.0f)
{
    this->start();
    this->moveToThread(this);

    QEventLoop loop;
    QMetaObject::invokeMethod(this, "onInit", Qt::QueuedConnection, Q_ARG(QEventLoop*, &loop));
    loop.exec();
    qDebug() << "speak inited.";
}

TextSpeak::~TextSpeak()
{
    qDebug() << __FUNCTION__;
    QTimer::singleShot(0, this, SLOT(onExitThread()));
    this->wait(5 * 1000);
}

void TextSpeak::speak(const QString &text)
{
    QMetaObject::invokeMethod(this, "onPushText", Qt::QueuedConnection, Q_ARG(QString, text));
}

void TextSpeak::setVoice(const QString &name)
{
    QMetaObject::invokeMethod(this, "onSetVoice", Qt::QueuedConnection, Q_ARG(QString, name));
}

void TextSpeak::setRate(int rate)
{
    if (rate < -10 || rate > 30) {
        return;
    }
    QMetaObject::invokeMethod(this, "onSetRate", Qt::QueuedConnection, Q_ARG(int, rate));
}

void TextSpeak::setVolume(int volume)
{
    if (volume < 0 || volume > 400) {
        return;
    }
    QMetaObject::invokeMethod(this, "onSetVolume", Qt::QueuedConnection, Q_ARG(int, volume));
}

void TextSpeak::onPushText(const QString &text)
{
    if (text.isEmpty()) {
        emit doSpeakBuffer(QByteArray(), QString());
        return;
    }

    if (voice_ == nullptr) {
        qWarning() << "speak failed1." << text;
        return;
    }
    auto hr = voice_->Speak(text.toStdWString().data(), SPF_DEFAULT, nullptr);
    if( FAILED(hr)) {
        qWarning() << "speak failed2." << text;
    }

    LARGE_INTEGER liTemp; memset(&liTemp, 0, sizeof(liTemp));
    ULARGE_INTEGER uiPos; memset(&uiPos, 0, sizeof(uiPos));
    stream_->Seek(liTemp, STREAM_SEEK_END, &uiPos);
    int dwSize = static_cast<int>(uiPos.QuadPart);
    QByteArray buffer(dwSize, 0);

    LARGE_INTEGER liTemp1; memset(&liTemp1, 0, sizeof(liTemp1));
    stream_->Seek(liTemp1, STREAM_SEEK_SET, &uiPos);
    stream_->Read(buffer.data(), static_cast<ULONG>(dwSize), nullptr);

    stream_->Seek(liTemp1, STREAM_SEEK_SET, &uiPos);
    ULARGE_INTEGER cl;
    memset(&cl, 0, sizeof(cl));
    stream_->SetSize(cl);

    //调整响度
    qint16 *bufferPtr = reinterpret_cast<qint16 *>(buffer.data());
    int countUint = buffer.size() / static_cast<int>(sizeof(qint16));
    for (int i = 0; i < countUint; ++i) {
        qint16 value = bufferPtr[i];
        qint32 value32 =  static_cast<qint32>(static_cast<float>(value) * volumnIncrease_);
        if (value32 < 32767 && value32 > -32768) {
            bufferPtr[i] = static_cast<qint16>(value32);
        } else if (value32 > 32767) {
            bufferPtr[i] = 32767;
        } else if (value32 < -32768) {
            bufferPtr[i] = -32768;
        }
    }

    soundtouch::SoundTouch touch;
    touch.setSampleRate(16000);
    touch.setChannels(1);
    touch.setTempo(static_cast<double>(speedIncrease_));

    uint samples = static_cast<uint>(static_cast<unsigned int>(buffer.size()) / sizeof(soundtouch::SAMPLETYPE));
    touch.putSamples(reinterpret_cast<soundtouch::SAMPLETYPE*>(buffer.data()), samples);

    uint nb = 0;

    bool emited = false;
    do
    {
        QByteArray bufferNew(dwSize, 0);
        // 接收处理后的sample
        nb = touch.receiveSamples(reinterpret_cast<soundtouch::SAMPLETYPE*>(bufferNew.data()), samples);
        if (nb != 0) {
            emited = true;
            emit doSpeakBuffer(bufferNew.mid(0, static_cast<int>(nb * sizeof(soundtouch::SAMPLETYPE))), text);
        }

    } while (nb != 0);
    if (!emited) {
        emit doSpeakBuffer(QByteArray(), QString());
    }
}

void TextSpeak::onSetVoice(const QString &name)
{
    IEnumSpObjectTokens *tokens = nullptr;
    auto hr = SpEnumTokens(SPCAT_VOICES, nullptr, nullptr, &tokens);
    if( FAILED(hr)) {
        return;
    }


    ULONG count = 0;
    tokens->GetCount(&count);
    for (ULONG i = 0; i < count; ++i) {
        ISpObjectToken *token = nullptr;
        tokens->Item(i, &token);

        wchar_t *temp = nullptr;
        SpGetDescription(token, &temp);
        if (QString::fromWCharArray(temp).contains(name)) {
            voice_->SetVoice(token);
            qDebug() << "set voice:" << QString::fromWCharArray(temp);
            break;
        }
    }
}

void TextSpeak::onSetRate(int rate)
{
    if (rate <= 10) {
        speedIncrease_ = 1.0f;
        voice_->SetRate(rate);
        qDebug() << "set rate:" << rate;
    } else {
        voice_->SetRate(10);
        speedIncrease_ = rate / 10.0f;
        qDebug() << "set rate:" << rate << " and increase:" << speedIncrease_;
    }
}

void TextSpeak::onSetVolume(int volume)
{
    if (volume > 100) {
        volumnIncrease_ = volume / 100.0f;
        voice_->SetVolume(100);
        qDebug() << "set volume:" << volume << " and increase:" << volumnIncrease_;
    } else {
        volumnIncrease_ = 1.0f;
        voice_->SetVolume(static_cast<USHORT>(volume));
        qDebug() << "set volume:" << volume;
    }
}

void TextSpeak::onInit(QEventLoop *loop)
{
    voice_ = nullptr;
    ::CoInitialize(nullptr);

    auto hr = CoCreateInstance(CLSID_SpVoice, nullptr, CLSCTX_ALL,
                               IID_ISpVoice, reinterpret_cast<void **>(&voice_));
    if( FAILED(hr)) {
        qWarning() << "error:" << __LINE__;
        return;
    }

    CComPtr<ISpStreamFormat> cpOldStream;
    hr = voice_->GetOutputStream(&cpOldStream);
    if( FAILED(hr)) {
        qWarning() << "error:" << __LINE__;
        return;
    }
    CSpStreamFormat OriginalFmt;
    OriginalFmt.AssignFormat(cpOldStream);

    ISpStream *spstream = nullptr;
    hr = CoCreateInstance(CLSID_SpStream, nullptr, CLSCTX_ALL, IID_ISpStream, reinterpret_cast<void **>(&spstream));
    if( FAILED(hr)) {
        qWarning() << "error:" << __LINE__;
        return;
    }

    hr = CreateStreamOnHGlobal(nullptr, true, &stream_);
    if( FAILED(hr)) {
        qWarning() << "error:" << __LINE__;
        return;
    }

    WAVEFORMATEX wavf = *OriginalFmt.WaveFormatExPtr();
    hr = spstream->SetBaseStream(stream_, OriginalFmt.FormatId(), &wavf);
    if( FAILED(hr)) {
        qWarning() << "error:" << __LINE__;
        return;
    }

    hr = voice_->SetOutput(spstream, TRUE);
    if( FAILED(hr)) {
        qWarning() << "error:" << __LINE__;
        return;
    }
    voice_->SetVolume(100);

    qDebug() << "init successed.";

    QTimer::singleShot(0, loop, SLOT(quit()));
}

void TextSpeak::onExitThread()
{
    qDebug() << __FUNCTION__;
    voice_->Release();
    voice_ = nullptr;
    stream_->Release();
    stream_ = nullptr;

    ::CoUninitialize();
    this->exit();
}
